
#include <stdlib.h>
#include <float.h>
#include <memory.h>
#include "../basic/operator.h"

int type_priority(char *type) {
	if (strcmp(type, INTEGER_TYPE) == 0) {
		return 0;
	} else if (strcmp(type, FRACTION_TYPE) == 0) {
		return 1;
	} else if (strcmp(type, DECIMAL_TYPE) == 0) {
		return 2;
	} else if (strcmp(type, COMPLEX_TYPE) == 0) {
		return 3;
	} else {
		return -1;
	}
}

Cell* combine_convert(Scheme *sc, Cell *x, Cell *y) {
	int type_x = type_priority(x->_type);
	int type_y = type_priority(y->_type);
	int reverse = (type_x < type_y);
	Cell *a;
	Cell *b;
	Cell *temp;
	if (!reverse) {
		a = x;
		b = y;
	} else {
		a = y;
		b = x;
	}
	//b -> a
	if (strcmp(a->_type, FRACTION_TYPE) == 0) {
		if (strcmp(b->_type, INTEGER_TYPE) == 0) {
			b = int_to_fraction(sc, b);
		} else {
			//do nothing
		}
	} else if (strcmp(a->_type, DECIMAL_TYPE) == 0) {
		if (strcmp(b->_type, INTEGER_TYPE) == 0) {
			b = int_to_decimal(sc, b);
		} else if (strcmp(b->_type, FRACTION_TYPE) == 0) {
			b = fraction_to_decimal(sc, b);
		} else {
			//do nothing
		}
	} else if (strcmp(a->_type, COMPLEX_TYPE) == 0) {
		if (type_of(sc, b, REAL_TYPE)) {
			b = real_to_complex(sc, b);
		} else {
			//do nothing
		}
	} else {
		//do nothing
	}
	if (reverse) {
		temp = a;
		a = b;
		b = temp;
	}
	return cons(sc, a, b);
}

int is_zero_number(Cell* x) {
    if (strcmp(x->_type, INTEGER_TYPE) == 0) {
        return (x->_longValue == 0);
    }
    if (strcmp(x->_type, FRACTION_TYPE) == 0) {
        return (car(x)->_longValue == 0);
    }
    if (strcmp(x->_type, DECIMAL_TYPE) == 0) {
        return equals_double(x->_doubleValue, 0.0);
    }
    if (strcmp(x->_type, COMPLEX_TYPE) == 0) {
        return is_zero_number(car(x)) && is_zero_number(cdr(x));
    }
    return FALSE;
}

int eqn(Cell *a, Cell *b) {
    if (strcmp(a->_type, INTEGER_TYPE) == 0 && strcmp(b->_type, INTEGER_TYPE) == 0) {
        return (a->_longValue == b->_longValue);
    }
    if (strcmp(a->_type, FRACTION_TYPE) == 0 && strcmp(b->_type, FRACTION_TYPE) == 0) {
        return (car(a)->_longValue == car(b)->_longValue && cdr(a)->_longValue == cdr(b)->_longValue);
    }
    if (strcmp(a->_type, DECIMAL_TYPE) == 0 && strcmp(b->_type, DECIMAL_TYPE) == 0) {
        return equals_double(a->_doubleValue, b->_doubleValue);
    }
    if (strcmp(a->_type, COMPLEX_TYPE) == 0 && strcmp(b->_type, COMPLEX_TYPE) == 0) {
        return (eqn(car(a), car(b)) && eqn(cdr(a), cdr(b)));
    }
    return FALSE;
}

Cell* number_add(Scheme *sc, Cell* x, Cell* y) {
    TYPE_CHECK_FOR_CELL(sc, x, NUMBER_TYPE);
    TYPE_CHECK_FOR_CELL(sc, y, NUMBER_TYPE);
    Cell* c = combine_convert(sc, x, y);
    Cell* x1 = car(c);
    Cell* y1 = cdr(c);
	if (type_of(sc, x1, REAL_TYPE)) {
        return real_add(sc, x1, y1);
    } else if (strcmp(x1->_type, COMPLEX_TYPE) == 0) {
        return complex_add(sc, x1, y1);
    } else {
        return E_NOT_A_NUMBER;
    }
}

Cell* number_mul(Scheme *sc, Cell* x, Cell* y) {
    TYPE_CHECK_FOR_CELL(sc, x, NUMBER_TYPE);
    TYPE_CHECK_FOR_CELL(sc, y, NUMBER_TYPE);
    Cell* c = combine_convert(sc, x, y);
    Cell* x1 = car(c);
    Cell* y1 = cdr(c);
    if (type_of(sc, x1, REAL_TYPE)) {
        return real_mul(sc, x1, y1);
    } else if (strcmp(x1->_type, COMPLEX_TYPE) == 0) {
        return complex_mul(sc, x1, y1);
    } else {
        return E_NOT_A_NUMBER;
    }
}

Cell* number_sub(Scheme *sc, Cell* x, Cell* y) {
    TYPE_CHECK_FOR_CELL(sc, x, NUMBER_TYPE);
    TYPE_CHECK_FOR_CELL(sc, y, NUMBER_TYPE);
    Cell* c = combine_convert(sc, x, y);
    Cell* x1 = car(c);
    Cell* y1 = cdr(c);
    if (type_of(sc, x1, REAL_TYPE)) {
        return real_sub(sc, x1, y1);
    } else if (strcmp(x1->_type, COMPLEX_TYPE) == 0) {
        return complex_sub(sc, x1, y1);
    } else {
        return E_NOT_A_NUMBER;
    }
}

Cell* number_div(Scheme *sc, Cell* x, Cell* y) {
    TYPE_CHECK_FOR_CELL(sc, x, NUMBER_TYPE);
    TYPE_CHECK_FOR_CELL(sc, y, NUMBER_TYPE);
    if (is_zero_number(y)) {
        return E_DIVISION_BY_ZERO;
    }
    Cell* c = combine_convert(sc, x, y);
    Cell* x1 = car(c);
    Cell* y1 = cdr(c);
    if (type_of(sc, x1, REAL_TYPE)) {
        return real_div(sc, x1, y1);
    } else if (strcmp(x1->_type, COMPLEX_TYPE) == 0) {
        return complex_div(sc, x1, y1);
    } else {
        return E_NOT_A_NUMBER;
    }
}

Cell* op_numeq(Scheme *sc) {
	Cell* value = first(sc->args);
	Cell* x = sc->args;
	TYPE_CHECK(sc, x, NUMBER_TYPE);
	for (x = cdr(x); x != &g_nil; x = cdr(x)) {
		TYPE_CHECK(sc, car(x), NUMBER_TYPE);
		if (!eqn(car(x), value)) {
			sc->returnValue = &g_false;
			return pop_callstack(sc);
		}
	}
	sc->returnValue = &g_true;
	return pop_callstack(sc);
}

// (+ x y ...)
Cell* op_add(Scheme *sc) {
	Cell* value = first(sc->args);
    TYPE_CHECK(sc, value, NUMBER_TYPE);
	Cell* x;
	for (x = rest(sc->args); x != &g_nil; x = cdr(x)) {
		Cell* y = car(x);
		TYPE_CHECK(sc, y, NUMBER_TYPE);
		value = number_add(sc, value, y);
	}
	sc->returnValue = value;
	return pop_callstack(sc);
}

// (* x y ...)
Cell* op_mul(Scheme *sc) {
	Cell* value = first(sc->args);
	TYPE_CHECK(sc, value, NUMBER_TYPE);
	Cell* x;
	for (x = rest(sc->args); x != &g_nil; x = cdr(x)) {
		TYPE_CHECK(sc, car(x), NUMBER_TYPE);
		value = number_mul(sc, value, car(x));
	}
	sc->returnValue = value;
	return pop_callstack(sc);
}

// (- x y ...)
Cell* op_sub(Scheme *sc) {
	Cell* value = first(sc->args);
	TYPE_CHECK(sc, value, NUMBER_TYPE);
	Cell* x;
	if (rest(sc->args) == &g_nil) {
		sc->returnValue = number_sub(sc, n_zero, value);
		return pop_callstack(sc);
	} else {
		for (x = rest(sc->args); x != &g_nil; x = cdr(x)) {
			TYPE_CHECK(sc, car(x), NUMBER_TYPE);
			value = number_sub(sc, value, car(x));
		}
	}
	sc->returnValue = value;
	return pop_callstack(sc);
}

// (/ x y ...)
Cell* op_div(Scheme *sc) {
	Cell* value = first(sc->args);
	TYPE_CHECK(sc, value, NUMBER_TYPE);
	Cell* x;
	if (rest(sc->args) == &g_nil) {
		sc->returnValue = number_div(sc, n_one, value);
		return pop_callstack(sc);
	} else {
		for (x = rest(sc->args); x != &g_nil; x = cdr(x)) {
			TYPE_CHECK(sc, car(x), NUMBER_TYPE);
			if (is_zero_number(car(x))) {
				sc->returnValue = E_DIVISION_BY_ZERO;
				return pop_callstack(sc);
			} else {
				value = number_div(sc, value, car(x));
			}
		}
	}
	sc->returnValue = value;
	return pop_callstack(sc);
}

#define NUMBER_STR_LENGTH 64

static char* long_to_string(long value) {
	char *p = (char *) malloc(NUMBER_STR_LENGTH * sizeof(char));
	snprintf(p, NUMBER_STR_LENGTH, "%ld", value);
	return p;
}

static char* double_to_string(double value) {
	char *p = (char *) malloc(NUMBER_STR_LENGTH * sizeof(char));
	snprintf(p, NUMBER_STR_LENGTH, "%f", value);
	return p;
}

static char* fraction_to_string(long num, long denom) {
	char *p = (char *) malloc(NUMBER_STR_LENGTH * sizeof(char));
	snprintf(p, NUMBER_STR_LENGTH, "%ld/%ld", num, denom);
	return p;
}

static char* complex_to_string(char* real, char* imag) {
	char *p = (char *) malloc(NUMBER_STR_LENGTH * sizeof(char));
	snprintf(p, NUMBER_STR_LENGTH, "%s+%si", real, imag);
	return p;
}

char* number_to_string(Scheme *sc, Cell* num) {
	if (type_of(sc, num, INTEGER_TYPE)) {
		return long_to_string(num->_longValue);
	} else if (type_of(sc, num, DECIMAL_TYPE)) {
		return double_to_string(num->_doubleValue);
	} else if (type_of(sc, num, FRACTION_TYPE)) {
		return fraction_to_string(car(num)->_longValue, cdr(num)->_longValue);
	} else if (type_of(sc, num, COMPLEX_TYPE)) {
		return complex_to_string(number_to_string(sc, car(num)), number_to_string(sc, cdr(num)));
	} else {
		return "NaN";
	}
}

// (number->string number)
// (number->string number base) //TODO
Cell* op_number_to_string(Scheme *sc) {
	Cell* num = first(sc->args);
	TYPE_CHECK(sc, num, NUMBER_TYPE);
	sc->returnValue = make_string(sc, number_to_string(sc, num));
	return pop_callstack(sc);
}

void init_number_operators(Scheme *sc) {
	ADD_OPERATOR(sc, "+", add);
	ADD_OPERATOR(sc, "*", mul);
	ADD_OPERATOR(sc, "-", sub);
	ADD_OPERATOR(sc, "/", div);
	ADD_OPERATOR(sc, "=", numeq);
	ADD_OPERATOR(sc, "number->string", number_to_string);
}
